﻿using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Helper
{
    /// <summary>
    /// Socket客户端内核
    /// Heng 2019-10-09
    /// </summary>
    public class SocketClient_Kenel
    {
        #region 构造函数
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="_ip_port"></param>
        /// <param name="_reg_info">注册信息</param>
        public SocketClient_Kenel(string[] _ip_port)
        {
            if (_ip_port == null) return;
            if (_ip_port.Length < 2) return;
            ip = _ip_port[0];
            port = int.Parse(_ip_port[1]);
        }
        /// <summary>
        /// 初始化
        /// </summary>
        public void Init()
        {
            Connect();
            KeepAlive();//开启保活线程
        }
        #endregion

        #region 属性
        private Socket s = null;
        private string ip = string.Empty;
        private int port = 0;
        /// <summary>
        /// 最后心跳时间
        /// 默认值是一个必过期的时间
        /// 与当前时间相差大于心跳超时参数即可
        /// </summary>
        private DateTime Latest_HeartBeat { get; set; } = DateTime.Now.AddDays(-10);
        /// <summary>
        /// 心跳超时参数
        /// 单位：秒
        /// </summary>
        protected int KeepAlive_Timeout { get; set; } = 10;
        /// <summary>
        /// 心跳检查连接状态的间隔时间
        /// 单位：秒
        /// </summary>
        private int KeepAlive_Interval { get; set; } = 5;
        /// <summary>
        /// 常驻线程控制
        /// 影响：保活线程
        /// </summary>
        private CancellationTokenSource cts { get; set; }
        #endregion

        #region 心跳保活（独立线程）
        /// <summary>
        /// 实时心跳监测
        /// </summary>
        /// <returns></returns>
        public void KeepAlive()
        {
            Task.Factory.StartNew(() =>
            {
                cts = new CancellationTokenSource();
                Thread.Sleep(KeepAlive_Interval * 1000); //首次等待,并开始检查连接状态
                while (!cts.IsCancellationRequested)
                {
                    if (!IsAlive)
                    {
                        Helper_Log.Error("Error", $"Socket_Error_{ip}({port})", $"检测到超时，开始重连...");
                        Task.Factory.StartNew(() =>//重连
                        {
                            Close();
                            Thread.Sleep(1000);
                            Connect();
                        });
                    }
                    Thread.Sleep(KeepAlive_Interval * 1000);
                }
            }, TaskCreationOptions.LongRunning);
        }
        /// <summary>
        /// 是否仍在连接
        /// </summary>
        public bool IsAlive
        {
            get
            {
                if (s == null) return false;
                TimeSpan ts = DateTime.Now - Latest_HeartBeat;
                return ts.TotalSeconds > KeepAlive_Timeout ? false : true;
            }
        }
        #endregion

        #region 连接 - 开关
        /// <summary>
        /// 开启连接
        /// </summary>
        private void Connect()
        {
            try
            {
                //实例化 套接字 （ip4寻址协议，流式传输，TCP协议）
                s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                s.ReceiveBufferSize = 1024 * 1024 * 10;
                IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ip), port);
                s.BeginConnect(endpoint, asyncResult =>//将 监听套接字  绑定到 对应的IP和端口
                {
                    try
                    {
                        s.EndConnect(asyncResult);
                        Latest_HeartBeat = DateTime.Now;//心跳：连接
                        RecMsg();
                        HandleConnected?.Invoke(this);
                    }
                    catch (Exception ex)
                    {
                        Helper_Log.Error("Error", $"Socket_Error_{ip}({port})", "连接异常：" + ex.Message);
                    }
                }, null);
            }
            catch (Exception ex)
            {
                Helper_Log.Error("Error", $"Socket_Error_{ip}({port})", "初始化连接异常" + ex.Message);
            }
        }
        /// <summary>
        /// 关闭连接
        /// </summary>
        public void Close()
        {
            try
            {
                if (s != null)
                {
                    if (s.Connected) { s.Shutdown(SocketShutdown.Both); }
                    s.Close();
                    s = null;
                }
            }
            catch (Exception ex)
            {
                Helper_Log.Error("Error", $"Socket_Error_{ip}({port})", "关闭异常" + ex);
            }
            finally
            {
                GC.Collect();
            }
        }
        #endregion

        #region 接收数据
        /// <summary>
        /// 已接收的数据队列
        /// </summary>
        public ConcurrentQueue<byte[]> q_rec { get; set; } = new ConcurrentQueue<byte[]>();
        /// <summary>
        /// 开始接受客户端消息（递归）
        /// </summary>
        private void RecMsg()
        {
            try
            {
                if (IsAlive == false) return;
                byte[] buffer = new byte[1024 * 1024 * 10]; //缓冲区大小为10M数据
                s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, asyncResult =>
                {
                    try
                    {
                        int length = s.EndReceive(asyncResult);
                        if (length > 0)
                        {
                            Latest_HeartBeat = DateTime.Now;//心跳：接收

                            byte[] recBytes = new byte[length];
                            Array.Copy(buffer, 0, recBytes, 0, length);
                            q_rec.Enqueue(recBytes);  //为了保证数据接收顺序，先入队列
                        }
                        Thread.Sleep(1);//降低CPU占用率
                        RecMsg();//进行下一轮接受（递归）
                    }
                    catch (Exception ex) { Helper_Log.Error("Error", $"Socket_Error_{ip}({port})", "检测到已被断开并释放，等待重连...详情：" + ex.Message); }
                }, null);
            }
            catch (Exception ex) { Helper_Log.Error("Error", $"Socket_Error_{ip}({port})", "检测到已被断开并释放，等待重连...详情：" + ex.Message); }
        }
        #endregion

        #region 发送
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bytes">数据字节</param>
        public void Send(byte[] bytes)
        {
            if (IsAlive == false) return;
            try
            {
                if (s.Poll(-1, SelectMode.SelectWrite))
                {
                    s.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult =>
                    {
                        try
                        {
                            int length = s.EndSend(asyncResult);
                            if (length > 0)
                            {
                                Latest_HeartBeat = DateTime.Now;//心跳：发送
                            }
                            HandleSended?.BeginInvoke(bytes, this, null, null);
                        }
                        catch (Exception ex)
                        {
                            Helper_Log.Error("Error", "Socket_Error", "【" + ip + ":" + port + "】发送异常：【" + Encoding.UTF8.GetString(bytes) + "】原因：" + ex.Message);
                        }
                    }, null);
                }
                else
                {
                    Helper_Log.Error("Error", "Socket_Error", "【" + ip + ":" + port + "】Poll SelectWrite 后发现不可发送，退出发送递归");
                }
            }
            catch (Exception ex)
            {
                Helper_Log.Error("Error", "Socket_Error", "【" + ip + ":" + port + "】首次发送异常，稍后重发：【" + Encoding.UTF8.GetString(bytes) + "】原因：" + ex.Message);
            }
        }
        /// <summary>
        /// 发送字符串（默认使用UTF-8编码）
        /// </summary>
        /// <param name="msgStr">字符串</param>
        public void Send(string msgStr)
        {
            Send(Encoding.UTF8.GetBytes(msgStr));
        }
        /// <summary>
        /// 发送字符串（使用自定义编码）
        /// </summary>
        /// <param name="msgStr">字符串消息</param>
        /// <param name="encoding">使用的编码</param>
        public void Send(string msgStr, Encoding encoding)
        {
            Send(encoding.GetBytes(msgStr));
        }
        #endregion

        #region 事件处理
        /// <summary>
        /// 客户端连接建立后回调
        /// </summary>
        public Action<SocketClient_Kenel> HandleConnected { get; set; }
        /// <summary>
        /// 客户端连接发送消息后回调
        /// </summary>
        public Action<byte[], SocketClient_Kenel> HandleSended { get; set; }
        #endregion

        #region 释放
        /// <summary>
        /// 释放该通信
        /// </summary>
        public void Dispose()
        {
            cts.Cancel();//关闭所有常驻线程
            Close();
        }
        #endregion
    }
}
